/* $Id: nocem.c,v 1.4 2004/11/11 12:04:22 sby Exp $ */

/*
  NoCeM-INNBBSD
  Yen-Ming Lee <leeym@civil.ncku.edu.tw>
  NCMparse(), NCMverify(), NCMcancel(): return 0 success, otherwise fail;
*/

#include "innbbsconf.h"
#include "bbslib.h"
#include "inntobbs.h"
#include "nocem.h"
#include <stdarg.h>

char NCMVER[8];
char ISSUER[STRLEN];
char TYPE[8];
char ACTION[8];
char NCMID[STRLEN];
char COUNT[8];
char THRESHOLD[STRLEN];
char KEYID[16];
char SPAMMID_NOW[STRLEN];
char SPAMMID[MAXSPAMMID][STRLEN];

int NNTP = -1;
FILE *NNTPrfp = NULL;
FILE *NNTPwfp = NULL;
char NNTPbuffer[1024];

int num_spammid = 0;
char errmsg[1024] = "nothing";

/* ------------------------------------------------------------------ */
/* NCM initial and maintain                                           */
/* ------------------------------------------------------------------ */

static int
ncm_bytypecmp(a,b)
ncmperm_t **a, **b;
{
  return strcasecmp((*a)->type, (*b)->type);
}

static int
ncmcmp(a,b)
ncmperm_t *a, *b;
{
  return strcasecmp(a->issuer, b->issuer);
}

#if 0
ncmperm_t *search_issuer(issuer)
char *issuer;
{
  ncmperm_t ncmt, *find;
  ncmt.issuer = "*";
  find = (ncmperm_t*)bsearch((char*)&ncmt, NCMPERM, NCMCOUNT, sizeof(ncmperm_t), ncmcmp);
  if (find)
    return find;
  ncmt.issuer = issuer;
  find = (ncmperm_t*)bsearch((char*)&ncmt, NCMPERM, NCMCOUNT, sizeof(ncmperm_t), ncmcmp);
  return find;
}
#else
ncmperm_t *search_issuer(issuer)
char *issuer;
{
  ncmperm_t ncmt, *find;
  int i;
  for (i = 0; i < NCMCOUNT; i++)
  {
    find = &NCMPERM[i];
    if (!strcmp(find->issuer, "*"))
      return find;
    if (!strcmp(issuer,find->issuer))
      return find;
  }
  return NULL;
}
#endif

int
readNCMfile(inndhome)
char *inndhome;
{
  FILE *fp;
  char buff[1024];
  struct stat st;
  int i, count;
  char *ptr, *ncmpermptr;

  sprintf(buff,"%s/ncmperm.bbs", inndhome);
  fp = fopen(buff,"r");
  if (fp == NULL) {
     fprintf(stderr,"read fail %s",buff);
     return -1;
  }
  if (fstat(fileno(fp),&st) != 0) {
      fprintf(stderr,"stat fail %s", buff);
      return -1;
  }
  if (NCMPERM_BUF == NULL) {
      NCMPERM_BUF = (char*) mymalloc( st.st_size +1);
  } else {
      NCMPERM_BUF = (char*) myrealloc( NCMPERM_BUF, st.st_size +1);
  }
  i = 0, count =0;
  while (fgets(buff, sizeof buff, fp) != NULL) {
     if (buff[0] == '#') continue;
     if (buff[0] == '\n') continue;
     strcpy(NCMPERM_BUF+i, buff);
     i += strlen(buff);
     count ++;
  }
  fclose(fp);
  if (NCMPERM == NULL) {
     NCMPERM = (ncmperm_t*) mymalloc(sizeof(ncmperm_t) * (count+1));
     NCMPERM_BYTYPE = (ncmperm_t**) mymalloc(sizeof(ncmperm_t*) * (count+1));
  } else {
     NCMPERM = (ncmperm_t*) myrealloc(NCMPERM, sizeof(ncmperm_t) * (count+1));
     NCMPERM_BYTYPE = (ncmperm_t**) myrealloc(NCMPERM_BYTYPE, sizeof(ncmperm_t*) * (count+1));
  }
  NCMCOUNT = 0;
  for (ptr = NCMPERM_BUF; (ncmpermptr = (char*)strchr(ptr,'\n')) != NULL; ptr = ncmpermptr +1, NCMCOUNT++) {
      char *nptr;
      *ncmpermptr = '\0';
      NCMPERM[NCMCOUNT].issuer = "";
      NCMPERM[NCMCOUNT].type = "";
      NCMPERM[NCMCOUNT].perm = 0;
      NCMPERM_BYTYPE[NCMCOUNT] = NCMPERM+NCMCOUNT;
      for (nptr= ptr ;*nptr && (*nptr == '\t'); ) nptr++;
      if (*nptr == '\0') continue;
      NCMPERM[NCMCOUNT].issuer = nptr;
      for (nptr++; *nptr && !(*nptr == '\t'); ) nptr++;
      if (*nptr == '\0') continue;
      *nptr = '\0';
      for (nptr++ ;*nptr && (*nptr == '\t'); ) nptr++;
      if (*nptr == '\0') continue;
      NCMPERM[NCMCOUNT].type = nptr;
      for (nptr++; *nptr && !(*nptr == '\t'); ) nptr++;
      if (*nptr == '\0') continue;
      *nptr = '\0';
      for (nptr++;*nptr && (*nptr == '\t'); ) nptr++;
      if (*nptr == '\0') continue;
      NCMPERM[NCMCOUNT].perm = (strstr(nptr, "y") || strstr(nptr, "Y"));
      for (nptr++; *nptr && !strchr("\r\n",*nptr); ) nptr++;
      /*if (*nptr == '\0') continue;*/
      *nptr = '\0';
  }
  qsort(NCMPERM, NCMCOUNT, sizeof(ncmperm_t), ncmcmp);
  qsort(NCMPERM_BYTYPE, NCMCOUNT, sizeof(ncmperm_t*), ncm_bytypecmp);
  //NCMregister();
  return 0;
}

NCMupdate(char *issuer, char *type)
{
  FILE *fp;
  char buff[1024];

  sprintf(buff,"%s/ncmperm.bbs", INNDHOME);
  if (!isfile(buff))
  {
    if ((fp = fopen(buff, "w")) == NULL)
    {
      fprintf(stderr,"write fail %s",buff);
      return -1;
    }
    fprintf(fp, "# This is ncmperm.bbs, it's auto-generated by program for first time\n");
    fprintf(fp, "# The columns *MUST* be separated by [TAB]\n");
    fprintf(fp, "# If you wanna accept someone's NoCeM notice, change his perm from \'no\' to \'yes\'\n");
    fprintf(fp, "# put \"*\" in Issuer column means to match all\n");
    fprintf(fp, "# Any questions ? please e-mail %s\n", LeeymEMAIL);
    fprintf(fp, "# Issuer\t\tType\tPerm\n");
    fflush(fp);
    fclose(fp);
    bbslog("NCMupdate create %s\n", buff);
  }
  if ((fp = fopen(buff, "a")) == NULL)
  {
    fprintf(stderr,"attach fail %s",buff);
    return -1;
  }
  fprintf(fp, "%s\t\t%s\tno\n", issuer, type);
  fflush(fp);
  fclose(fp);
  bbslog("NCMupdate add Issuer: %s , Type: %s\n", ISSUER, TYPE);
  sleep(1);
  if (readNCMfile(INNDHOME) == -1)
    bbslog("fail to readNCMfile\n");
}

int
tcpcommand(char *fmt, ...)
{
  va_list ap;
  char *ptr;

  va_start(ap, fmt);
  vfprintf(NNTPwfp, fmt, ap);
  fprintf(NNTPwfp, "\r\n");
  fflush(NNTPwfp);

  fgets(NNTPbuffer, sizeof NNTPbuffer, NNTPrfp);
  ptr = strchr(NNTPbuffer, '\r');
  if (ptr)
    *ptr = '\0';
  ptr = strchr(NNTPbuffer, '\n');
  if (ptr)
    *ptr = '\0';
  va_end(ap);
  return atoi(NNTPbuffer);
}

int
NCMregister()
{
  int status;
  time_t now = time(NULL);

  if ((NNTP = inetclient(LeeymBBS, "7777", "tcp")) < 0)
  {
    bbslog("NCMregister :Err: server %s %s error: cant connect\n", LeeymBBS, "7777");
    return 0;
  }

  if (!(NNTPrfp = fdopen(NNTP, "r")) || !(NNTPwfp = fdopen(NNTP, "w")))
  {
    bbslog("NCMregister :Err: fdopen failed\n");
    return 0;
  }

  fgets(NNTPbuffer, sizeof NNTPbuffer, NNTPrfp);
  if (atoi(NNTPbuffer) != 200)
  {
    bbslog("NCMregister :Err: server error: %s", NNTPbuffer);
    return 0;
  }
  status = tcpcommand("ADDHIST <%d$%s> NCMregister/%s/%s",
        now, ADMINUSER, VERSION, NCMINNBBSVER);
  status = tcpcommand("QUIT");
  fclose(NNTPwfp);
  fclose(NNTPrfp);
  close(NNTP);
}

/* ------------------------------------------------------------------ */
/* PGP verify                                                         */
/* ------------------------------------------------------------------ */

int
run_pgp(char *cmd, FILE **in, FILE **out)
{
  int pin[2], pout[2], child_pid;
  char PGPPATH[80];

  sprintf(PGPPATH, "%s/.pgp", BBSHOME);
  setenv("PGPPATH", PGPPATH, 1);

  *in = *out = NULL;

  pipe(pin);
  pipe(pout);

  if(!(child_pid = fork()))
  {
    /*We're the child.*/
    close(pin[1]);
    dup2(pin[0], 0);
    close(pin[0]);

    close(pout[0]);
    dup2(pout[1], 1);
    close(pout[1]);

    execl("/bin/sh", "sh", "-c", cmd, NULL);
    _exit(127);
  }
  /*Only get here if we're the parent.*/
  close(pout[1]);
  *out = fdopen(pout[0], "r");

  close(pin[0]);
  *in = fdopen(pin[1], "w");

  return(child_pid);
}

int
verify_buffer(char *buf, char *passphrase)
{
  FILE *pgpin, *pgpout;
  char tmpbuf[1024] = " ";
  int ans = NOPGP;

  setenv("PGPPASSFD", "0", 1);
//  run_pgp("/usr/local/bin/pgpv -f +batchmode=1 +OutputInformationFD=1",
//      &pgpin, &pgpout);
  run_pgp("/usr/local/bin/pgpv -f +batchmode=1",&pgpin, &pgpout);
  if(pgpin && pgpout)
  {
    fprintf(pgpin, "%s\n", passphrase); /*Send the passphrase in, first*/
    bzero(passphrase, strlen(passphrase)); /*Burn the passphrase*/
    fprintf(pgpin, "%s", buf);
    fclose(pgpin);

    *buf = '\0';
    fgets(tmpbuf, sizeof(tmpbuf), pgpout);
    while(!feof(pgpout))
    {
      strcat(buf, tmpbuf);
      fgets(tmpbuf, sizeof(tmpbuf), pgpout);
    }

    wait(NULL);

    fclose(pgpout);
  }

  if (strstr(buf, "Good signature made"))
  {
    strcpy(errmsg, "Good signature");
    ans = PGPGOOD;
  }
  else if (strstr(buf, "BAD signature made"))
  {
    strcpy(errmsg, "BAD signature");
    ans = PGPBAD;
  }
  else if (strcpy(tmpbuf, strstr(buf, "Signature by unknown keyid:")))
  {
    sprintf(errmsg, "%s ", strtok(tmpbuf, "\r\n"));
    strcpy(KEYID, strrchr(tmpbuf, ' ') + 1);
    ans = PGPUN;
  }

  unsetenv("PGPPASSFD");
  return ans;
}


/*
int
NCMverify()
{
  int ans;
  char passphrase[80] = "Haha, I am Leeym..";
  ans = verify_buffer(BODY, passphrase);
  return ans;
}
*/

/* ------------------------------------------------------------------ */
/* parse NoCeM Notice Headers/Body                                    */
/* ------------------------------------------------------------------ */

int
readNCMheader(char *line)
{
  if (!strncasecmp(line, "Version", strlen("Version")))
  {
    strcpy(NCMVER, line + strlen("Version") + 2);
    if (!strstr(NCMVER, "0.9"))
    {
      sprintf(errmsg, "unknown version: %s", NCMVER);
      return P_FAIL;
    }
  }
  else if (!strncasecmp(line, "Issuer", strlen("Issuer")))
  {
    strcpy(ISSUER, line + strlen("Issuer") + 2);
    FROM = ISSUER;
  }
  else if (!strncasecmp(line, "Type", strlen("Type")))
  {
    strcpy(TYPE, line + strlen("Type") + 2);
  }
  else if (!strncasecmp(line, "Action", strlen("Action")))
  {
    strcpy(ACTION, line + strlen("Action") + 2);
    if (!strstr(ACTION, "hide"))
    {
      sprintf(errmsg, "unsupported action: %s", ACTION);
      return P_FAIL;
    }
  }
  else if (!strncasecmp(line, "Notice-ID", strlen("Notice-ID")))
  {
    strcpy(NCMID, line + strlen("Notice-ID") + 2);
  }
  else if (!strncasecmp(line, "Count", strlen("Count")))
  {
    strcpy(COUNT, line + strlen("Count") + 2);
  }
  else if (!strncasecmp(line, "Threshold", strlen("Threshold")))
  {
    strcpy(THRESHOLD, line + strlen("Threshold") + 2);
  }

  return P_OKAY;
}

int
readNCMbody(char *line)
{
  char buf[1024], *group;
  strcpy(buf, line);

  if (!strstr(buf, "\t"))
    return P_FAIL;

  group = strrchr(line, '\t') + 1;

  if (buf[0] == '<' && strstr(buf, ">"))
  {
    strtok(buf, "\t");
    strcpy(SPAMMID_NOW, buf);
  }

  if (num_spammid && !strcmp(SPAMMID[num_spammid-1], SPAMMID_NOW))
    return 0;

  if (search_group(group))
    strcpy(SPAMMID[num_spammid++], SPAMMID_NOW);
}

int
NCMparse()
{
  char *fptr, *ptr;
  int type = TEXT;

  for (fptr = BODY, ptr = strchr(fptr, '\r'); ptr != NULL && *ptr != '\0'; fptr = ptr + 2, ptr = strchr(fptr, '\r'))
  {
    int ch = *ptr;
    *ptr = '\0';

    if (num_spammid > MAXSPAMMID)
      return P_OKAY;

    if (!strncmp(fptr, "@@", 2))
    {
      if (strstr(fptr, "BEGIN NCM HEADERS"))
        type = NCMHDR;
      else if (strstr(fptr, "BEGIN NCM BODY"))
      {
        if (NCMVER && ISSUER && TYPE && ACTION && COUNT && NCMID)
        {
          ncmperm_t *ncmt;
          ncmt = (ncmperm_t *) search_issuer(ISSUER);
          if (ncmt == NULL)
          {
            NCMupdate(ISSUER, TYPE);
            sprintf(errmsg, "unknown issuer: %s, %s", ISSUER, MSGID);
            return P_UNKNOWN;
          }
          if (ncmt->perm == 0)
          {
            sprintf(errmsg, "disallow issuer: %s, %s", ISSUER, MSGID);
            return P_DISALLOW;
          }
        }
        else
        {
          strcpy(errmsg, "HEADERS syntax not correct");
          return P_FAIL;
        }
        type = NCMBDY;
      }
      else if (strstr(fptr, "END NCM BODY"))
        type = TEXT;
      else
      {
        strcpy(errmsg, "NCM Notice syntax not correct");
        return P_FAIL;
      }
      *ptr = ch;
      continue;
    }

    if (type == NCMHDR && readNCMheader(fptr) == P_FAIL)
      return P_FAIL;
    else if (type == NCMBDY)
      readNCMbody(fptr);
    *ptr = ch;
  }
  if (NCMVER && ISSUER && TYPE && ACTION && COUNT && NCMID)
      return P_OKAY;
  else
  {
    strcpy(errmsg, "HEADERS syntax not correct");
    return P_FAIL;
  }

  strcpy(errmsg, "I don't know..");
  return P_FAIL;
}

int
NCMcancel()
{
  int i, rel, num_ok, num_fail;
  for (i = rel = num_ok = num_fail = 0; i < num_spammid; i++)
  {
    rel = cancel_article_front(SPAMMID[i]);
    bbslog("SPAM : %s\n",SPAMMID[i]); 
    if (rel)
      num_fail++;
    else
      num_ok++;
  }
  bbslog("NCMcancel %s %s, count:%d spam:%d, ok:%d fail:%d\n",
        ISSUER, MSGID, atoi(COUNT), num_spammid, num_ok, num_fail);
  return 0;
}

/* ------------------------------------------------------------------ */
/* NoCeM-innbbsd                                                      */
/* ------------------------------------------------------------------ */

initial_nocem()
{
  bzero(SPAMMID[0], strlen(SPAMMID[0]) * num_spammid);
  num_spammid = 0;
  bzero(SPAMMID_NOW, strlen(SPAMMID_NOW));
}

int
receive_nocem()
{
  int rel;
  ncmperm_t *ncmt;

  initial_nocem();

  rel = NCMparse();

  if (rel != P_OKAY)
  {
    if (rel != P_DISALLOW)
      bbslog("NCMparse %s\n", errmsg);
    return 0;
  }

  if (!num_spammid)
    return 0;

//  rel = NCMverify();
/*
  if (rel != PGPGOOD)
  {
    bbslog("NCMverify %s, %s, %s\n", errmsg, MSGID, ISSUER);
    return 0;
  }
*/
  return NCMcancel();
}
